package skewreduce.lsst;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import skewreduce.lib.IPoint3D;
import skewreduce.lib.ITuple;
import skewreduce.lib.Partition;


// FIXME should generalize to ArrayPartition
public class Image2DPartition extends Partition {
	Coord mp;
	Coord Mp;
	int axis;
	int split;
	byte skinMask; // only flagged region will propagate

	// 000 001 : L x-axis
	// 000 010 : L y-axis
	// 000 100 : R x-axis
	// 001 000 : R y-axis

	public Image2DPartition() {
		mp = new Coord();
		Mp = new Coord();
	}

	public Image2DPartition(int w,int h) {
		mp = new Coord(0,0);
		Mp = new Coord(w,h);
	}
	
	public Image2DPartition(String spec) {
		super(spec);
	}
	
	public Image2DPartition(long id,int lv,Coord l,Coord r,byte m) {
		super(id,lv);
		mp = l;
		Mp = r;
		skinMask = m;
	}

	@Override
	public <T extends ITuple> boolean atCore(T p, Number eps) {
		int x = p.getInt(0);
		int y = p.getInt(1);
		int b = eps.intValue();
		return mp.getX() + b <= x && x < Mp.getX() - b && mp.getY() + b <= y && y < Mp.getY() - b;
	}

	@Override
	public <T extends ITuple> boolean contains(T p) {
		int x = p.getInt(0);
		int y = p.getInt(1);
		return mp.getX() <= x && x < Mp.getX() && mp.getY() <= y && y < Mp.getY();
	}
	
	public int getWidth() { return Mp.getX() - mp.getX(); }
	public int getHeight() { return Mp.getY() - mp.getY(); }
	public int getSize(int i) { return Mp.getInt(i) - mp.getInt(i); }
	public int getMin(int i) { return mp.getInt(i); }
	public int getMax(int i) { return Mp.getInt(i); }
	public int getSplitAxis() { return axis; }
	public int getSplit() { return split; }

	@Override
	public double getVolume() {
		return getWidth() * getHeight();
	}

	@Override
	protected Partition newInstance() {
		return new Image2DPartition();
	}

	@Override
	public void readInternalFields(DataInput in) throws IOException {
		mp = new Coord();
		Mp = new Coord();
		
		mp.readFields(in);
		Mp.readFields(in);
		axis = in.readInt();
		split = in.readInt();
	}

	@Override
	protected void setInternal(String[] flds) {
		mp = new Coord(flds[0]);
		Mp = new Coord(flds[1]);
		axis = Integer.parseInt(flds[2]);
		split = Integer.parseInt(flds[3]);
	}

	@Override
	protected void setInternal(Partition org) {
		mp = ((Image2DPartition)org).mp;
		Mp = ((Image2DPartition)org).Mp;
		axis = ((Image2DPartition)org).axis;
		split = ((Image2DPartition)org).split;
	}

	@Override
	public <T extends ITuple> int split(T[] arr, int from, int to,
			TerminalCondition cond) {
		throw new UnsupportedOperationException();
	}
	
    @Override
    public int uniformSplit(TerminalCondition cond) {
        int maxLv = getLevel();

        // eval split condition
        if ( cond.continueSplit(0,getLevel()) ) {
        	axis = ( getHeight() > 1 ) ? 1 : 0; // we first split along y axis then x axis
            split = ( mp.getInt(axis) + Mp.getInt(axis) ) >>> 1; 

            int lMx = ( axis == 0 ) ? split : Mp.getX();
            int lMy = ( axis == 1 ) ? split : Mp.getY();

            int rmx = ( axis == 0 ) ? split : mp.getX();
            int rmy = ( axis == 1 ) ? split : mp.getY();

            left = new Image2DPartition( 
                (getID()<<1),
                getLevel()+1,
                mp,
                new Coord(lMx,lMy),
                (byte)(skinMask|0x08)
            );

            right = new Image2DPartition( 
                (getID()<<1)|0x01,
                getLevel()+1,
                new Coord(rmx,rmy),
                Mp,
                (byte)(skinMask|0x02)
            );

            maxLv = Math.max(
                    left.uniformSplit(cond),
                    right.uniformSplit(cond) 
            );
        }
        return maxLv;
    }
    
	@Override
	protected <T extends ITuple> Partition whichPartition(T x) {
		int v = x.getInt(axis);
		return ( v < split ) ? left : right;
	}

	@Override
	public void writeInternalFields(DataOutput out) throws IOException {
		mp.write(out);
		Mp.write(out);
		out.writeInt(axis);
		out.writeInt(split);
	}
	

	@Override
	protected void toSpecInternal(StringBuilder buf) {
		buf.append(mp.toSpec());
		buf.append(';');
		buf.append(Mp.toSpec());
		buf.append(';');
		buf.append(axis);
		buf.append(';');
		buf.append(split);
	}

	@Override
	protected String toStringInternal() {
		return String.format("%s;%s;%d;%d",mp,Mp,axis,split);
	}
	
	@Override
	public int getArity() { return 2; }

	@Override
	public Partition[] split(int axis, Number v, float lprob) {
		// ignore lprob
		this.axis = axis;
	    split = v.intValue();

        int lMx = ( axis == 0 ) ? split : Mp.getX();
        int lMy = ( axis == 1 ) ? split : Mp.getY();

        int rmx = ( axis == 0 ) ? split : mp.getX();
        int rmy = ( axis == 1 ) ? split : mp.getY();

        left = new Image2DPartition( 
            (getID()<<1),
            getLevel()+1,
            mp,
            new Coord(lMx,lMy),
            (byte)(skinMask|0x08)
        );

        right = new Image2DPartition( 
            (getID()<<1)|0x01,
            getLevel()+1,
            new Coord(rmx,rmy),
            Mp,
            (byte)(skinMask|0x02)
        );

		return new Partition[] { left, right };
	}
	
	@Override
	public final <T extends ITuple> boolean atSkin(T q, Number eps) {
		if ( skinMask == 0x00 )
			// none needs to be included
			return false;
		
		boolean result = super.atSkin(q,eps);
		if ( skinMask >= 0x0f || ! result ) {
			return result;
		}
		
    	IPoint3D p = (IPoint3D)q;
    	float r = eps.floatValue();
    	
    	// filter out unnecessary surfaces
    	
    	// 000 001 : L x-axis
    	// 000 010 : L y-axis
    	// 000 100 : R x-axis
    	// 001 000 : R y-axis

    	if ( (skinMask & 0x01) > 0 && p.getX() <= mp.getX()+r ) return true;
    	if ( (skinMask & 0x02) > 0 && p.getY() <= mp.getY()+r ) return true;
    	if ( (skinMask & 0x04) > 0 && p.getX() >= Mp.getX()-r ) return true;
    	if ( (skinMask & 0x08) > 0 && p.getY() >= Mp.getY()-r ) return true;
    	
    	return false;
	}

	@Override
	public <T extends ITuple> boolean atMergeSurface(T q, Number eps) {
		int r = eps.intValue();
		int v = q.getInt(axis);
		return split - r <= v && v <= split + r;
	}

	@Override
	public Partition getMergingArea(Partition l, Partition r, Number eps) {
		throw new UnsupportedOperationException();
	}

	@Override
	public Number getLength(int fld) {
		return ( fld == 0 ) ? getWidth() : getHeight();
	}

	@Override
	public Partition[] split(int[] axis, float[] v, float lprob) {
		if ( axis.length > 1 || v.length > 1 )
			throw new UnsupportedOperationException();
		
		return split(axis[0],v[0],lprob);
	}
}
